
#include <iostream>
#include <fstream>
#include <string>
#include <sstream>
#include <vector>
#include <cstdlib>
#include <cstring>
#include <map>

#include "Obj3DLoader.hpp"
#include "Scene.hpp"
#include "Math.hpp"

#define _DEBUG_
using namespace com::toxiclabs::tsoc;
using namespace std;


/**
* Credits goes to cplusplus.com :)
*/
string replaceStrChar(string str, const string& replace, char ch) {

 
  size_t found = str.find_first_of(replace);

  while (found != string::npos) { 
    str[found] = ch; 
    found = str.find_first_of(replace, found+1); 
  }

  return str;
}

vector<string> SplitStr(string str,char separator)
{
	vector<string> stack;
	
	string tmp;
	
	for(int n=0;n<str.length();n++)
	{
		if(str[n]==separator)
		{
			
			stack.push_back(tmp);
			tmp="";
		}
		else
		{
			tmp=tmp+str[n];
		}
	}
	stack.push_back(tmp);
	
	return stack;
}

void Obj3DLoader::Load(const char * filename,Scene * scene)
{
	ifstream file;
	string buffer;
	string tmp;
	string tmp2;
	stringstream ss (stringstream::in | stringstream::out);
	vector <float> vertices;
	vector <float> normals;
	vector <float> uvs;
	vector <int> faces;
	vector <int> face_normals;
	vector <int> face_uvs;
	vector <int> mat_ids;
	
	int mat_id=0;

	float vdata[3];
	int vnum[3],tnum[3],nnum[3];
	
	file.open(filename,ifstream::in);
	while(!file.eof())
	{
		getline(file,buffer);
		vector<string> parts = SplitStr(buffer,' ');
			
			
		//cout<<"line ["<<parts[0]<<"]:"<<buffer<<endl;
		tmp=parts[0];
		//comment
		if(tmp=="#")
		{
			
			
		}
		
		//material lib
		if(tmp=="mtllib")
		{
			tmp=parts[1];
			Obj3DLibLoader::Load(tmp.c_str(),&scene->materials);
			
		}
		
		//object
		if(tmp=="o")
		{
			tmp=parts[1];
			
		}
		
		//material
		if(tmp=="usemtl")
		{
			tmp=parts[1];
			for(int n=0;n<scene->materials.size();n++)
			{
				if(scene->materials[n]->name==tmp)
				{
					mat_id=n;
					break;
				}
			}
			
			
		}
		
		//vertex
		if(tmp=="v")
		{
			
			vdata[0]=atof(parts[1].c_str());
			vdata[1]=atof(parts[2].c_str());
			vdata[2]=atof(parts[3].c_str());
			vertices.push_back(vdata[0]);
			vertices.push_back(vdata[1]);
			vertices.push_back(vdata[2]);
			
		}
		
		//normal
		if(tmp=="vn")
		{
			vdata[0]=atof(parts[1].c_str());
			vdata[1]=atof(parts[2].c_str());
			vdata[2]=atof(parts[3].c_str());
			normals.push_back(vdata[0]);
			normals.push_back(vdata[1]);
			normals.push_back(vdata[2]);
			
		}
		
		//uv
		if(tmp=="vt")
		{
			vdata[0]=atof(parts[1].c_str());
			vdata[1]=atof(parts[2].c_str());
			uvs.push_back(vdata[0]);
			uvs.push_back(vdata[1]);
			
			if(vdata[0]<0 || vdata[1]<0)
			{
				cout<<"Warning: negative UV at "<<buffer<<endl;
			}
		
		}
		
		//face, expecting triangles
		if(tmp=="f")
		{
			memset(tnum,0,sizeof(int)*3);
			memset(vnum,0,sizeof(int)*3);
			memset(nnum,0,sizeof(int)*3);
			
			vector<string> vx_parts = SplitStr(parts[1],'/');
			
			vnum[0]=atoi(vx_parts[0].c_str());
			tnum[0]=atoi(vx_parts[1].c_str());
			nnum[0]=atoi(vx_parts[2].c_str());
			
			vx_parts = SplitStr(parts[2],'/');
			
			vnum[1]=atoi(vx_parts[0].c_str());
			tnum[1]=atoi(vx_parts[1].c_str());
			nnum[1]=atoi(vx_parts[2].c_str());
			
			vx_parts = SplitStr(parts[3],'/');
			
			vnum[2]=atoi(vx_parts[0].c_str());
			tnum[2]=atoi(vx_parts[1].c_str());
			nnum[2]=atoi(vx_parts[2].c_str());
						
			
			mat_ids.push_back(mat_id);
			
			faces.push_back(vnum[0]-1);
			faces.push_back(vnum[1]-1);
			faces.push_back(vnum[2]-1);
			
			face_uvs.push_back(tnum[0]-1);
			face_uvs.push_back(tnum[1]-1);
			face_uvs.push_back(tnum[2]-1);	
							
			face_normals.push_back(nnum[0]-1);
			face_normals.push_back(nnum[1]-1);
			face_normals.push_back(nnum[2]-1);
			/*	
			cout<<"UV id A:"<<tnum[0]-1<<endl;
			cout<<"UV id B:"<<tnum[1]-1<<endl;
			cout<<"UV id C:"<<tnum[2]-1<<endl;
			*/
		}
			
		
	}//file
	
	file.close();
	
#ifdef _DEBUG_
	cout<<"* vertices parsed:"<<vertices.size()<<endl;
	cout<<"* normals parsed:"<<normals.size()<<endl;
	cout<<"* faces parsed:"<<faces.size()<<endl;
	cout<<"* uvs parsed:"<<uvs.size()<<endl;
	cout<<"* building triangle list...";
#endif

	Triangle * tri;
	float vac[4];
	float vab[4];
	
	for(int n=0;n<faces.size();n+=3)
	{
		tri = new Triangle();
		tri->vertex[0].x=vertices[(3*faces[n])+0];
		tri->vertex[0].y=vertices[(3*faces[n])+1];
		tri->vertex[0].z=-vertices[(3*faces[n])+2];
		tri->vertex[0].w=1.0f;
		
		tri->vertex[1].x=vertices[(3*faces[n+1])+0];
		tri->vertex[1].y=vertices[(3*faces[n+1])+1];
		tri->vertex[1].z=-vertices[(3*faces[n+1])+2];
		tri->vertex[1].w=1.0f;
		
		tri->vertex[2].x=vertices[(3*faces[n+2])+0];
		tri->vertex[2].y=vertices[(3*faces[n+2])+1];
		tri->vertex[2].z=-vertices[(3*faces[n+2])+2];
		tri->vertex[2].w=1.0f;
		
		tri->normal[0].x=normals[(3*face_normals[n])+0];
		tri->normal[0].y=normals[(3*face_normals[n])+1];
		tri->normal[0].z=-normals[(3*face_normals[n])+2];
		tri->normal[0].w=0.0f;
		
		tri->normal[1].x=normals[(3*face_normals[n+1])+0];
		tri->normal[1].y=normals[(3*face_normals[n+1])+1];
		tri->normal[1].z=-normals[(3*face_normals[n+1])+2];
		tri->normal[1].w=0.0f;
		
		tri->normal[2].x=normals[(3*face_normals[n+2])+0];
		tri->normal[2].y=normals[(3*face_normals[n+2])+1];
		tri->normal[2].z=-normals[(3*face_normals[n+2])+2];
		tri->normal[2].w=0.0f;
		
		if(uvs.size()>0)
		{
			tri->texture[0].u=uvs[2*face_uvs[n]+0];
			tri->texture[0].v=uvs[2*face_uvs[n]+1];
			
			tri->texture[1].u=uvs[2*face_uvs[n+1]+0];
			tri->texture[1].v=uvs[2*face_uvs[n+1]+1];
			
			tri->texture[2].u=uvs[2*face_uvs[n+2]+0];
			tri->texture[2].v=uvs[2*face_uvs[n+2]+1];
		}
		/*
		cout<<"uv A "<<tri->uv[0]<<","<<tri->uv[1]<<endl;
		cout<<"uv B "<<tri->uv[2]<<","<<tri->uv[3]<<endl;
		cout<<"uv C "<<tri->uv[4]<<","<<tri->uv[5]<<endl;
		*/ 
		
		
		Vector vac;
		Vector vab;
		
		vac = tri->vertex[0] - tri->vertex[1];
		vab = tri->vertex[0] - tri->vertex[2];
		
		tri->pnormal = vac ^ vab;
		tri->pnormal.Normalize();
		tri->pnormal.w=0.0f; //do I really need this?
		
		tri->material_id=mat_ids[n/3];
		
		scene->triangles.push_back(tri);
	}

#ifdef _DEBUG_	
	cout<<"done"<<endl;
	
	cout<<"* Created:"<<scene->triangles.size()<<" triangles"<<endl;
#endif
}


void Obj3DLibLoader::Load(const char * filename,vector<Material *> * material_list)
{
	ifstream file;
	string buffer;
	stringstream ss (stringstream::in | stringstream::out);
	string tmp;
	string tmp2;
	Material * material;
	float vdata[3];
	int idata;
	
	map<string,Texture *>textures;
	
	file.open(filename,ifstream::in);
	while(!file.eof())
	{
		getline(file,buffer);
		
		vector<string> parts = SplitStr(buffer,' ');
		
		tmp=parts[0];
		
		//comment
		if(tmp=="#")
		{
			
		}
		
		//material lib
		if(tmp=="newmtl")
		{
			tmp=parts[1];
			material=new Material();
			material->name=tmp;
			material_list->push_back(material);
			
		}
		
		//Diffuse
		if(tmp=="Kd")
		{
			vdata[0]=atof(parts[1].c_str());
			vdata[1]=atof(parts[2].c_str());
			vdata[2]=atof(parts[3].c_str());
			
			material->diffuse.r=vdata[0];
			material->diffuse.g=vdata[1];
			material->diffuse.b=vdata[2];
			
		}
		
		//Specular
		if(tmp=="Ks")
		{
			vdata[0]=atof(parts[1].c_str());
			vdata[1]=atof(parts[2].c_str());
			vdata[2]=atof(parts[3].c_str());
			
			material->specular.r=vdata[0];
			material->specular.g=vdata[1];
			material->specular.b=vdata[2];
			
		}
		
		//map_kd
		if(tmp=="map_Kd")
		{
			map<string,Texture *>::iterator it = textures.find(parts[1]);
			if(it==textures.end())
			{
				textures[parts[1].c_str()]=Texture::Load(parts[1].c_str());;
			}
			
			
			material->flags = material->flags | TSOC_MATERIAL_TEXTURE_COLOR;
			material->texture_kd=textures[parts[1].c_str()];
		}
		
		if(tmp=="bump")
		{
			map<string,Texture *>::iterator it = textures.find(parts[1]);
			if(it==textures.end())
			{
				textures[parts[1].c_str()]=Texture::Load(parts[1].c_str());;
			}
			
			
			material->flags = material->flags | TSOC_MATERIAL_TEXTURE_NORMAL;
			material->texture_normal=textures[parts[1].c_str()];
		}
		
		//shading flags
		if(tmp=="illum")
		{
			idata = atoi(parts[1].c_str());
			
			if(idata==3)
				material->flags = material->flags | TSOC_MATERIAL_REFLECTION;
				
		}
		
	}
	
	file.close();
}
